﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Media;

namespace SDF
{
    public class SmartDocumentConverter
    {
        #region Static Properties

        public static SmartDocumentConverter Default
        {
            get 
            {
                if (_Default == null)
                    _Default = new SmartDocumentConverter();

                return _Default; 
            }
        }
        private static SmartDocumentConverter _Default;

        #endregion

        #region Constructor

        protected SmartDocumentConverter()
        {
            ThrowErrors = true;
            Errors = new List<String>();
            Warnings = new List<String>();
        }

        #endregion

        #region Properties

        public bool ThrowErrors { get; set; }

        public List<String> Errors { get; private set; }

        public List<String> Warnings { get; private set; }

        #endregion

        #region ConvertFrom Methods

        public SmartDocument ConvertFrom(System.Windows.Documents.FlowDocument document)
        {
            Errors = new List<String>();
            Warnings = new List<String>();

            if (document == null)
                return null;

            var result = new SmartDocument();
            CopyDocumentProperties(result, document);
            ConvertFromBlocks(result, document.Blocks);
            return result;
        }

        protected virtual void ConvertFromBlocks(IBlockContainer result, System.Windows.Documents.BlockCollection blockCollection)
        {
            foreach (var block in blockCollection)
            {
                var convertedBlock = ConvertFromBlock(block);
                if (convertedBlock != null)
                    result.Blocks.Add(convertedBlock);
            }
        }

        protected virtual SmartBlock ConvertFromBlock(System.Windows.Documents.Block block)
        {
            if (block == null)
                throw new ArgumentNullException("block");

            var paragraph = block as System.Windows.Documents.Paragraph;
            if (paragraph != null)
                return ConvertFromParagraph(paragraph);

            var section = block as System.Windows.Documents.Section;
            if (section != null)
                return ConvertFromSection(section);

            var table = block as System.Windows.Documents.Table;
            if (table != null)
                return ConvertFromTable(table);

            var list = block as System.Windows.Documents.List;
            if (list != null)
                return ConvertFromList(list);

            RaiseError("SmartDocumentConverter does not support '{0}'.", block.GetType());
            return null;
        }

        protected virtual Paragraph ConvertFromParagraph(System.Windows.Documents.Paragraph paragraph)
        {
            var result = new Paragraph();
            CopyBlockProperties(result, paragraph);
            ConvertFromInlines(result, paragraph.Inlines);
            result.KeepTogether = paragraph.KeepTogether;
            return result;
        }

        protected virtual Section ConvertFromSection(System.Windows.Documents.Section section)
        {
            var result = new Section();
            CopyBlockProperties(result, section);
            ConvertFromBlocks(result, section.Blocks);
            if (section.Margin == new Thickness(0))
            {
                result.Margin = new Thickness(double.NaN);
            }
            return result;
        }

        protected virtual Table ConvertFromTable(System.Windows.Documents.Table table)
        {
            var result = new Table();
            CopyBlockProperties(result, table);
            result.BorderBrush = table.BorderBrush;
            result.BorderThickness = table.BorderThickness;
            ConvertFromColumns(result, table.Columns);
            ConvertFromRowGroups(result, table.RowGroups);
            return result;
        }

        private void ConvertFromColumns(Table result, System.Windows.Documents.TableColumnCollection columns)
        {
            foreach (var column in columns)
            { 
                var newColumn = new TableColumn();
                newColumn.Background = column.Background;

                // FlowDocuments do not support "Auto" sizing of columns. It treats them as 1 star!
                if (column.Width.IsAuto)
                    newColumn.Width = new GridLength(1, GridUnitType.Star);
                else
                    newColumn.Width = column.Width;
                result.Columns.Add(newColumn);
            }
        }

        private void ConvertFromRowGroups(Table result, System.Windows.Documents.TableRowGroupCollection rowGroups)
        {
            foreach (var rowGroup in rowGroups)
            {
                foreach (var row in rowGroup.Rows)
                {
                    var newRow = new TableRow();
                    CopyTextElementProperties(newRow, row);
                    
                    // For some reason, all properties from the rowgroup (except the background)
                    // propagate to the row
                    if ((newRow.Background == null) && (rowGroup.Background != null))
                        newRow.Background = rowGroup.Background;
                    result.Rows.Add(newRow);

                    foreach (var cell in row.Cells)
                    {
                        var newCell = new TableCell();
                        CopyTextElementProperties(newCell, cell);
                        newCell.Padding = cell.Padding;
                        newCell.BorderBrush = cell.BorderBrush;
                        newCell.BorderThickness = cell.BorderThickness;
                        newCell.ColumnSpan = cell.ColumnSpan;
                        newCell.RowSpan = cell.RowSpan;
                        newRow.Cells.Add(newCell);
                        ConvertFromBlocks(newCell, cell.Blocks);
                    }
                }
            }
        }

        protected virtual List ConvertFromList(System.Windows.Documents.List list)
        {
            var result = new List();
            CopyBlockProperties(result, list);
            result.MarkerStyle = list.MarkerStyle;
            result.MarkerOffset = list.MarkerOffset;
            foreach (var item in list.ListItems)
            {
                var newItem = ConvertFromListItem(item);
                result.Items.Add(newItem);
            }
            return result;
        }

        private ListItem ConvertFromListItem(System.Windows.Documents.ListItem item)
        {
            var result = new ListItem();
            CopyTextElementProperties(result, item);
            result.Margin = item.Margin;
            result.Padding = item.Padding;
            result.TextAlignment = item.TextAlignment;
            ConvertFromBlocks(result, item.Blocks);
            return result;
        }

        protected virtual void CopyBlockProperties(SmartBlock target, System.Windows.Documents.Block source)
        {
            CopyTextElementProperties(target, source);
            target.BreakColumnBefore = source.BreakColumnBefore;
            target.BreakPageBefore = source.BreakPageBefore;
            target.FlowDirection = source.FlowDirection;
            target.Margin = source.Margin;
            target.Padding = source.Padding;
            target.TextAlignment = source.TextAlignment;
        }

        protected virtual void CopyTextElementProperties(SmartTextElement target, System.Windows.Documents.TextElement source)
        {
            target.Background = source.Background;
            target.FontFamily = source.FontFamily;
            target.FontSize = source.FontSize;
            target.FontStretch = source.FontStretch;
            target.FontStyle = source.FontStyle;
            target.FontWeight = source.FontWeight;
            target.Foreground = source.Foreground;
            target.TextEffects = source.TextEffects;
        }

        protected virtual void CopyDocumentProperties(SmartDocument target, System.Windows.Documents.FlowDocument source)
        {
            if (source.Background != null)
                target.Background = source.Background;
            target.FontFamily = source.FontFamily;
            target.FontSize = source.FontSize;
            target.FontStretch = source.FontStretch;
            target.FontStyle = source.FontStyle;
            target.FontWeight = source.FontWeight;
            target.Foreground = source.Foreground;
            target.TextEffects = source.TextEffects;
            target.ColumnGap = source.ColumnGap;
            target.ColumnWidth = source.ColumnWidth;
            target.ColumnRuleBrush = source.ColumnRuleBrush;
            target.ColumnRuleWidth = source.ColumnRuleWidth;
        }

        protected virtual void ConvertFromInlines(IInlineContainer result, System.Windows.Documents.InlineCollection inlineCollection)
        {
            foreach (var inline in inlineCollection)
            {
                var convertedInline = ConvertFromInline(inline);
                if (convertedInline != null)
                    result.Inlines.Add(convertedInline);
            }
        }

        protected virtual SmartInline ConvertFromInline(System.Windows.Documents.Inline inline)
        {
            if (inline == null)
                throw new ArgumentNullException("block");

            var run = inline as System.Windows.Documents.Run;
            if (run != null)
                return ConvertFromRun(run);

            var span = inline as System.Windows.Documents.Span;
            if (span != null)
                return ConvertFromSpan(span);

            var lineBreak = inline as System.Windows.Documents.LineBreak;
            if (lineBreak != null)
                return ConvertFromLineBreak(lineBreak);

            var container = inline as System.Windows.Documents.InlineUIContainer;
            if (container != null)
                return ConvertFromInlineUIContainer(container);

            var figure = inline as System.Windows.Documents.Figure;
            if (figure != null)
                return ConvertFromFigure(figure);

            RaiseError("SmartDocumentConverter does not support '{0}'.", inline.GetType());
            return null;
        }

        protected virtual TextRun ConvertFromRun(System.Windows.Documents.Run run)
        {
            var result = new TextRun();
            CopyInlineProperties(result, run);
            result.Text = run.Text;
            return result;
        }

        protected virtual Span ConvertFromSpan(System.Windows.Documents.Span span)
        {
            var result = new Span();
            CopyInlineProperties(result, span);
            ConvertFromInlines(result, span.Inlines);
            return result;
        }

        protected virtual LineBreak ConvertFromLineBreak(System.Windows.Documents.LineBreak lineBreak)
        {
            var result = new LineBreak();
            return result;
        }

        protected virtual UIElementContainer ConvertFromInlineUIContainer(System.Windows.Documents.InlineUIContainer container)
        {
            var result = new UIElementContainer();
            CopyInlineProperties(result, container);
            result.Child = container.Child;
            return result;
        }

        protected virtual Figure ConvertFromFigure(System.Windows.Documents.Figure figure)
        {
            var result = new Figure();
            CopyInlineProperties(result, figure);
            ConvertFromBlocks(result, figure.Blocks);

            result.Margin = figure.Margin;
            result.Padding = figure.Padding;
            result.Height = ConvertFigureHeight(figure.Height);
            result.Width = ConvertFigureWidth(figure.Width);

            Warnings.Add("The document contains a figure. Note that Figures in SDF are treated differently than Figures in FlowDocument. The content of the figure " +
                "will still render, although it may be positioned differently.");

            switch (figure.HorizontalAnchor)
            { 
                case FigureHorizontalAnchor.ColumnRight:
                case FigureHorizontalAnchor.ContentRight:
                case FigureHorizontalAnchor.PageRight:
                    result.Alignment = FigureAlignment.Right;
                    break;

                default:
                    result.Alignment = FigureAlignment.Left;
                    break;
            }
            return result;
        }

        protected virtual double ConvertFigureHeight(FigureLength figureLength)
        {
            if (figureLength.IsAuto)
                return double.NaN;

            if (figureLength.IsAbsolute)
                return figureLength.Value;

            Warnings.Add("SDF does not support relative Figure heights. Defaulting to 'Auto'.");

            return double.NaN;
        }

        protected virtual FigureWidth ConvertFigureWidth(FigureLength figureLength)
        {
            if (figureLength.IsAuto)
                return FigureWidth.Auto;

            if (figureLength.IsAbsolute)
                return new FigureWidth(figureLength.Value);

            Warnings.Add("SDF does not support Figure widths relative to Page, Content or Columns. Making the width relative to the paragraph instead.");

            return new FigureWidth(figureLength.Value, true);
        }


        protected virtual void CopyInlineProperties(SmartInline target, System.Windows.Documents.Inline source)
        {
            target.Background = source.Background;
            target.FontFamily = source.FontFamily;
            target.FontSize = source.FontSize;
            target.FontStretch = source.FontStretch;
            target.FontStyle = source.FontStyle;
            target.FontWeight = source.FontWeight;
            target.Foreground = source.Foreground;
            target.TextDecorations = source.TextDecorations;
            target.TextEffects = source.TextEffects;
        }

        #endregion

        #region Error and Warning Methods

        protected virtual void RaiseError(string message)
        {
            if (ThrowErrors)
                throw new NotSupportedException(message);
            else
                Errors.Add(message);
        }

        protected void RaiseError(string message, params object[] args)
        {
            message = String.Format(message, args);
            RaiseError(message);
        }

        #endregion
    }
}
